package org.pixelami.twisted.spread.pb
{
	import org.pixelami.twisted.internet.defer.Deferred;
	import org.pixelami.twisted.spread.flavors.Serializable;
	import org.pixelami.twisted.utils.__builtin__;
	
	//public class RemoteReference extends Serializable implements IUnjellyable
	public class RemoteReference extends Serializable 
	{
		/*
		This is a translucent reference to a remote object.
	
	    I may be a reference to a L{flavors.ViewPoint}, a
	    L{flavors.Referenceable}, or an L{IPerspective} implementor (e.g.,
	    pb.Avatar).  From the client's perspective, it is not possible to
	    tell which except by convention.
	
	    I am a \"translucent\" reference because although no additional
	    bookkeeping overhead is given to the application programmer for
	    manipulating a reference, return values are asynchronous.
	
	    See also L{twisted.internet.defer}.
	
	    @ivar broker: The broker I am obtained through.
	    @type broker: L{Broker}
	    */
	    public var luid:Object;
	    public var broker:Object;
	    public var doRefCount:Object;
	    public var perspective:Object;
	    public var disconnectCallbacks:Array;
		
		public function RemoteReference(perspective:Object = null, broker:Object = null, luid:Object = null, doRefCount:Object = null)
		{
			/*
			(internal) Initialize me with a broker and a locally-unique ID.
	
	        The ID is unique only to the particular Perspective Broker
	        instance.
	        */
			//trace("new Remote Reference :");
			//this.luid = luid;
	    	//this.broker = broker;
	    	//this.doRefCount = doRefCount;
	        //this.perspective = perspective;
	        //this.disconnectCallbacks = [];
	        //trace("perspective : "+perspective);
	        //trace("broker : "+broker);
	        //trace("luid : "+luid);
	        __init__(perspective, broker, luid, doRefCount);
	        
		}
		
		public function __init__(perspective:Object, broker:Object, luid:Object, doRefCount:Object):void
		{
			this.luid = luid;
	    	this.broker = broker;
	    	this.doRefCount = doRefCount;
	        this.perspective = perspective;
	        this.disconnectCallbacks = [];
		}
		
	    public function notifyOnDisconnect(callback:Function):void
	    {
	        /*
	        Register a callback to be called if our broker gets disconnected.
	        This callback will be called with one argument, this instance.
	        */
	        //assert callable(callback)
	        this.disconnectCallbacks.push(callback);
	        if(this.disconnectCallbacks.length == 1)
	            this.broker.notifyOnDisconnect(this._disconnected);
	    }
	
	    public function dontNotifyOnDisconnect(callback:Function):void
	    {
	        /*
	        Remove a callback that was registered with notifyOnDisconnect.
			*/
	        this.disconnectCallbacks.remove(callback);
	        if (!this.disconnectCallbacks)
	            this.broker.dontNotifyOnDisconnect(this._disconnected);
	    }
	
	    private function _disconnected():void
	    {
	        /*
	        Called if we are disconnected and have callbacks registered.
			*/
	        for each(var callback:Function in this.disconnectCallbacks)
	        {
	            callback(this);
	        }
	        this.disconnectCallbacks = null;
	    }
	    
	    public function remoteMessageReceived(broker:*, message:String, args:Array, kw:Object):Object
	    {
	        /*
	        """A remote message has been received.  Dispatch it appropriately.
	
	        The default implementation is to dispatch to a method called
	        'remote_messagename' and call it with the same arguments.
	        """
	        */
	        args = broker.unserialize(args)
	        kw = broker.unserialize(kw)
	        var method:Function = this.hasOwnProperty("remote_" + message) ? this["remote_" + message] : null;
	        
	        if (method == null)
	            throw new Error("No such method: remote_%s" + message);
	        try
	        {
	            //var state = method(*args, **kw);
	            var state:Object = method.apply(null,args);
	        }
	        catch(e: TypeError)
	        {
	            //log.msg("%s didn't accept %s and %s" % (method, args, kw));
	            //trace(message+" didn't accept "+args);
	            throw(e);
	        }
	        //__del__();
	        return broker.serialize(state, this.perspective)
	    }
		
		//NOTE perhaps this needs to return a PyTuple ?
	    override public function jellyFor(jellier:*):Object
	    {
	        /*
	        If I am being sent back to where I came from, serialize as a local backreference.
	        */
	        if (jellier.invoker)
	        {
	            if(this.broker != jellier.invoker)
	             	throw new Error("Can't send references to brokers other than their own.");
	            return ["local", this.luid];
	        }
	        else
	        {
	            return ["unpersistable", "References cannot be serialized"];
	        }
	    }
	
	    public function unjellyFor(unjellier:Object, unjellyList:Array):RemoteReference
	    {
	        this.__init__(unjellier.invoker.unserializingPerspective, unjellier.invoker, unjellyList[1], 1);
	        return this;
	        
	    }
	
	    //public function callRemote(_name, *args, **kw):void
	    public function callRemote(_name:String, ...args):Deferred
	    {
	        /*
	        Asynchronously invoke a remote method.
	
	        @type _name:   C{string}
	        @param _name:  the name of the remote method to invoke
	        @param args: arguments to serialize for the remote function
	        @param kw:  keyword arguments to serialize for the remote function.
	        @rtype:   L{twisted.internet.defer.Deferred}
	        @returns: a Deferred which will be fired when the result of
	                  this remote call is received.
	        */
	       
	        // note that we use '_name' instead of 'name' so the user can call
	        // remote methods with 'name' as a keyword parameter, like this:
	       	//  ref.callRemote("getPeopleNamed", count=12, name="Bob")

	        trace("RemoteReference callRemote");
	        return this.broker.sendMessage('',this.perspective, this.luid,_name, args, {});                              
	    }
	
	    public function remoteMethod(key:Object):RemoteMethod
	    {
	        /*
	        Get a L{RemoteMethod} for this key.
	        */
	        return new RemoteMethod(this, key);
	    }
	
	    public function __cmp__(other:Object):Boolean
	    {
	        /*
	        Compare me [to another L{RemoteReference}].
	        */
	        if(other is RemoteReference)
	        {
	            if(other.broker == this.broker)
	            {
	                return __builtin__.cmp(this.luid, other.luid);
	            }
	        }
	        return __builtin__.cmp(this.broker, other);
	    }
	
	    public function __hash__():Object
	    {
	        /*
	        Hash me.
	        */
	        return this.luid;
	    }
	
	    public function __del__():void
	    {
	        /*
	        Do distributed reference counting on finalization.
	        */
	        //trace("RemoteReference __del__ : "+doRefCount)
	        if(this.doRefCount > 0)
	        {
	        	//trace("sending decref");
	        	this.broker.sendDecRef(this.luid);
	        }
	            
	    }

	}
}